// SPDX-License-Identifier: GPL-2.0-or-later
 /*
 * lockfs.c
 *
 * Code which implements the kernel side of a minimal userspace
 * interface to our LOCK. This file handles the virtual file system
 * used for communication with userspace. Credit should go to ramfs,
 * which was a template for the fs side of this module.
 *
 * Copyright (C) 2003, 2004 Oracle.  All rights reserved.
 */

/* Simple VFS hooks based on: */
/*
 * Resizable simple ram filesystem for Linux.
 *
 * Copyright (C) 2000 Linus Torvalds.
 *               2000 Transmeta Corp.
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/backing-dev.h>
#include <linux/poll.h>

#include <linux/uaccess.h>

#include "lockfs.h"
#include "userdlm.h"
#include "useradl.h"
#include "adl/adlapi.h"
#define MLOG_MASK_PREFIX ML_LOCKFS
#include "cluster/masklog.h"


static const struct super_operations lockfs_ops;
static const struct file_operations lockfs_file_operations;
static const struct inode_operations lockfs_dir_inode_operations;
static const struct inode_operations lockfs_root_inode_operations;
static const struct inode_operations lockfs_file_inode_operations;
static struct kmem_cache *lockfs_inode_cache;

struct workqueue_struct *user_dlm_worker;

/* lockfs dirname */
#define LOCK_TYPE_ADL	"ADL"
#define LOCK_TYPE_DLM	"DLM"
#define ADL_CAPA_CAW	"CAW"
#define LOCK_TYPE_LEN	3
#define LOCK_CAPA_LEN	3

/* sync with ocfs2_lockglue.h */
#define OCFS2_LOCK_NL		0		/* null */
#define OCFS2_LOCK_PR		3	/* protected read */
#define OCFS2_LOCK_EX		5	/* exclusive */

/*
 * These are the ABI capabilities of lockfs.
 *
 * Over time, lockfs has added some features that were not part of the
 * initial ABI.  Unfortunately, some of these features are not detectable
 * via standard usage.  For example, Linux's default poll always returns
 * EPOLLIN, so there is no way for a caller of poll(2) to know when lockfs
 * added poll support.  Instead, we provide this list of new capabilities.
 *
 * Capabilities is a read-only attribute.  We do it as a module parameter
 * so we can discover it whether lockfs is built in, loaded, or even not
 * loaded.
 *
 * The ABI features are local to this machine's lockfs mount.  This is
 * distinct from the locking protocol, which is concerned with inter-node
 * interaction.
 *
 * Capabilities:
 * - bast	: EPOLLIN against the file descriptor of a held lock
 *		  signifies a bast fired on the lock.
 */
#define LOCKFS_CAPABILITIES "bast stackglue"
static int param_set_lockfs_capabilities(const char *val,
					const struct kernel_param *kp)
{
	mlog(ML_ERROR, "%s: readonly parameter\n", kp->name);
	return -EINVAL;
}
static int param_get_lockfs_capabilities(char *buffer,
					const struct kernel_param *kp)
{
	return strlcpy(buffer, LOCKFS_CAPABILITIES,
		       strlen(LOCKFS_CAPABILITIES) + 1);
}
module_param_call(capabilities, param_set_lockfs_capabilities,
		  param_get_lockfs_capabilities, NULL, 0444);
MODULE_PARM_DESC(capabilities, LOCKFS_CAPABILITIES);

/*
 * decodes a set of open flags into a valid lock level and a set of flags.
 * returns < 0 if we have invalid flags
 * flags which mean something to us:
 * O_RDONLY -> PRMODE level
 * O_WRONLY -> EXMODE level
 *
 * O_NONBLOCK -> NOQUEUE
 */
static int lockfs_decode_open_flags(int open_flags,
				   int *level,
				   int *flags)
{
	if (open_flags & (O_WRONLY|O_RDWR))
		*level = OCFS2_LOCK_EX;
	else
		*level = OCFS2_LOCK_PR;

	*flags = 0;
	if (open_flags & O_NONBLOCK)
		*flags |= DLM_LKF_NOQUEUE;

	return 0;
}

static int lockfs_file_open(struct inode *inode,
			   struct file *file)
{
	int status, level, flags;
	struct lockfs_filp_private *fp = NULL;
	struct lockfs_inode_private *ip;

	if (S_ISDIR(inode->i_mode))
		BUG();

	mlog(0, "open called on inode %lu, flags 0x%x\n", inode->i_ino,
		file->f_flags);

	status = lockfs_decode_open_flags(file->f_flags, &level, &flags);
	if (status < 0)
		goto bail;

	/* We don't want to honor O_APPEND at read/write time as it
	 * doesn't make sense for LVB writes. */
	file->f_flags &= ~O_APPEND;

	fp = kmalloc(sizeof(*fp), GFP_NOFS);
	if (!fp) {
		status = -ENOMEM;
		goto bail;
	}
	fp->fp_lock_level = level;

	ip = LOCKFS_I(inode);
	if (ip->ip_lock_stack == OCFS2_LOCK_DLM)
		status = user_dlm_cluster_lock(&ip->ip_lockres, level, flags);
	else
		status = user_adl_cluster_lock(&ip->ip_lockres, level, flags);

	if (status < 0) {
		/* this is a strange error to return here but I want
		 * to be able userspace to be able to distinguish a
		 * valid lock request from one that simply couldn't be
		 * granted. */
		if (flags & DLM_LKF_NOQUEUE && status == -EAGAIN)
			status = -ETXTBSY;
		kfree(fp);
		goto bail;
	}

	file->private_data = fp;
bail:
	return status;
}

static int lockfs_file_release(struct inode *inode,
			      struct file *file)
{
	int level, status;
	struct lockfs_inode_private *ip = LOCKFS_I(inode);
	struct lockfs_filp_private *fp = file->private_data;

	if (S_ISDIR(inode->i_mode))
		BUG();

	mlog(0, "close called on inode %lu\n", inode->i_ino);

	status = 0;
	if (fp) {
		level = fp->fp_lock_level;
		if (ip->ip_lock_stack == OCFS2_LOCK_DLM)
			user_dlm_cluster_unlock(&ip->ip_lockres, level);
		else if (ip->ip_lock_stack == OCFS2_LOCK_ADL)
			user_adl_cluster_unlock(&ip->ip_lockres, level);

		kfree(fp);
		file->private_data = NULL;
	}

	return 0;
}

/*
 * We do ->setattr() just to override size changes.  Our size is the size
 * of the LVB and nothing else.
 */
static int lockfs_file_setattr(struct dentry *dentry, struct iattr *attr)
{
	int error;
	struct inode *inode = d_inode(dentry);

	attr->ia_valid &= ~ATTR_SIZE;
	error = setattr_prepare(dentry, attr);
	if (error)
		return error;

	setattr_copy(inode, attr);
	mark_inode_dirty(inode);
	return 0;
}

static __poll_t lockfs_file_poll(struct file *file, poll_table *wait)
{
	__poll_t event = 0;
	struct inode *inode = d_inode(file->f_path.dentry);
	struct lockfs_inode_private *ip = LOCKFS_I(inode);

	poll_wait(file, &ip->ip_lockres.l_event, wait);

	spin_lock(&ip->ip_lockres.l_lock);
	if (ip->ip_lockres.l_flags & USER_LOCK_BLOCKED)
		event = EPOLLIN | EPOLLRDNORM;
	spin_unlock(&ip->ip_lockres.l_lock);

	return event;
}

static ssize_t lockfs_file_read(struct file *filp,
			       char __user *buf,
			       size_t count,
			       loff_t *ppos)
{
	int bytes_left;
	ssize_t readlen, got;
	char *lvb_buf;
	struct inode *inode = d_inode(filp->f_path.dentry);

	mlog(0, "inode %lu, count = %zu, *ppos = %llu\n",
		inode->i_ino, count, *ppos);

	if (LOCKFS_I(inode)->ip_lock_stack == OCFS2_LOCK_ADL) {
		mlog(ML_ERROR, "lockfs file can't read in ADL mode\n");
		return -EINVAL;
	}

	if (*ppos >= i_size_read(inode))
		return 0;

	if (!count)
		return 0;

	if (!access_ok(buf, count))
		return -EFAULT;

	/* don't read past the lvb */
	if ((count + *ppos) > i_size_read(inode))
		readlen = i_size_read(inode) - *ppos;
	else
		readlen = count;

	lvb_buf = kmalloc(readlen, GFP_NOFS);
	if (!lvb_buf)
		return -ENOMEM;

	got = user_dlm_read_lvb(inode, lvb_buf, readlen);
	if (got) {
		BUG_ON(got != readlen);
		bytes_left = __copy_to_user(buf, lvb_buf, readlen);
		readlen -= bytes_left;
	} else
		readlen = 0;

	kfree(lvb_buf);

	*ppos = *ppos + readlen;

	mlog(0, "read %zd bytes\n", readlen);
	return readlen;
}

static ssize_t lockfs_file_write(struct file *filp,
				const char __user *buf,
				size_t count,
				loff_t *ppos)
{
	int bytes_left;
	ssize_t writelen;
	char *lvb_buf;
	struct inode *inode = d_inode(filp->f_path.dentry);

	mlog(0, "inode %lu, count = %zu, *ppos = %llu\n",
		inode->i_ino, count, *ppos);

	if (LOCKFS_I(inode)->ip_lock_stack == OCFS2_LOCK_ADL) {
		mlog(ML_ERROR, "lockfs file can't write in ADL mode\n");
		return -EINVAL;
	}

	if (*ppos >= i_size_read(inode))
		return -ENOSPC;

	if (!count)
		return 0;

	if (!access_ok(buf, count))
		return -EFAULT;

	/* don't write past the lvb */
	if ((count + *ppos) > i_size_read(inode))
		writelen = i_size_read(inode) - *ppos;
	else
		writelen = count - *ppos;

	lvb_buf = kmalloc(writelen, GFP_NOFS);
	if (!lvb_buf)
		return -ENOMEM;

	bytes_left = copy_from_user(lvb_buf, buf, writelen);
	writelen -= bytes_left;
	if (writelen)
		user_dlm_write_lvb(inode, lvb_buf, writelen);

	kfree(lvb_buf);

	*ppos = *ppos + writelen;
	mlog(0, "wrote %zd bytes\n", writelen);
	return writelen;
}

static void lockfs_init_once(void *foo)
{
	struct lockfs_inode_private *ip =
		(struct lockfs_inode_private *) foo;

	ip->ip_conn = NULL;
	ip->ip_parent = NULL;
	ip->ip_lock_stack = OCFS2_LOCK_INVALID;

	inode_init_once(&ip->ip_vfs_inode);
}

static struct inode *lockfs_alloc_inode(struct super_block *sb)
{
	struct lockfs_inode_private *ip;

	ip = kmem_cache_alloc(lockfs_inode_cache, GFP_NOFS);
	if (!ip)
		return NULL;

	return &ip->ip_vfs_inode;
}

static void lockfs_i_callback(struct rcu_head *head)
{
	struct inode *inode = container_of(head, struct inode, i_rcu);

	kmem_cache_free(lockfs_inode_cache, LOCKFS_I(inode));
}

static void lockfs_destroy_inode(struct inode *inode)
{
	call_rcu(&inode->i_rcu, lockfs_i_callback);
}

static void lockfs_evict_inode(struct inode *inode)
{
	int status = 0;
	struct lockfs_inode_private *ip;

	clear_inode(inode);

	mlog(0, "inode %lu\n", inode->i_ino);

	ip = LOCKFS_I(inode);

	if (S_ISREG(inode->i_mode)) {
		if (ip->ip_lock_stack == OCFS2_LOCK_DLM)
			status = user_dlm_destroy_lock(&ip->ip_lockres);

		if (status < 0)
			mlog_errno(status);
		iput(ip->ip_parent);
		goto clear_fields;
	}

	mlog(0, "we're a directory, ip->ip_conn = 0x%p\n", ip->ip_conn);
	/* we must be a directory. If required, lets unregister the
	 * dlm context now. */
	if (ip->ip_conn) {
		if (ip->ip_lock_stack == OCFS2_LOCK_DLM)
			user_dlm_unregister(ip->ip_conn);
		else
			user_adl_unregister(ip->ip_conn);
	}

clear_fields:
	ip->ip_parent = NULL;
	ip->ip_conn = NULL;
}

static struct inode *lockfs_get_root_inode(struct super_block *sb)
{
	struct inode *inode = new_inode(sb);
	int mode = S_IFDIR | 0755;

	if (inode) {
		inode->i_ino = get_next_ino();
		inode->i_mode = mode;
		inode->i_uid = current_fsuid();
		inode->i_gid = current_fsgid();
		inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
		inc_nlink(inode);

		inode->i_fop = &simple_dir_operations;
		inode->i_op = &lockfs_root_inode_operations;
	}

	return inode;
}

static struct inode *lockfs_get_inode(struct inode *parent,
				     struct dentry *dentry,
				     int mode)
{
	struct super_block *sb = parent->i_sb;
	struct inode *inode = new_inode(sb);
	struct lockfs_inode_private *ip;

	if (!inode)
		return NULL;

	inode->i_ino = get_next_ino();
	inode->i_mode = mode;
	inode->i_uid = current_fsuid();
	inode->i_gid = current_fsgid();
	inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);

	ip = LOCKFS_I(inode);
	ip->ip_conn = LOCKFS_I(parent)->ip_conn;
	ip->ip_lock_stack = LOCKFS_I(parent)->ip_lock_stack;

	switch (mode & S_IFMT) {
	default:
		/* for now we don't support anything other than
		 * directories and regular files. */
		BUG();
		break;
	case S_IFREG:
		inode->i_op = &lockfs_file_inode_operations;
		inode->i_fop = &lockfs_file_operations;

		if (ip->ip_lock_stack == OCFS2_LOCK_DLM) {
			i_size_write(inode,  DLM_LVB_LEN);
			user_dlm_lock_res_init(&ip->ip_lockres, dentry);
		} else if (user_adl_lock_res_init(&ip->ip_lockres, dentry)) {
			iput(inode);
			return NULL;
		}

		/* released at clear_inode time, this insures that we
		 * get to drop the dlm reference on each lock *before*
		 * we call the unregister code for releasing parent
		 * directories. */
		ip->ip_parent = igrab(parent);
		BUG_ON(!ip->ip_parent);
		break;
	case S_IFDIR:
		inode->i_op = &lockfs_dir_inode_operations;
		inode->i_fop = &simple_dir_operations;

		/* directory inodes start off with i_nlink ==
		 * 2 (for "." entry) */
		inc_nlink(inode);
		break;
	}

	if (parent->i_mode & S_ISGID) {
		inode->i_gid = parent->i_gid;
		if (S_ISDIR(mode))
			inode->i_mode |= S_ISGID;
	}

	return inode;
}

static int lockfs_handle_dname(char *d_name,
		enum ocfs2_lock_class *lock_stack,
		char *domain_name)
{
	int status = 0;
	char *lock_type, *tmp_name = d_name;
	char *adl_capa;
	char *uuid;

	/* lock type name ADL/DLM */
	lock_type = strsep(&tmp_name, "_");
	if (!lock_type) {
		status = -EINVAL;
		goto bail;
	}

	if (!strncmp(lock_type, LOCK_TYPE_ADL, LOCK_TYPE_LEN + 1)) {
		/* ADL capability CAW */
		*lock_stack = OCFS2_LOCK_ADL;
		adl_capa = strsep(&tmp_name, "_");
		if (!adl_capa) {
			status = -EINVAL;
			goto bail;
		}
		/* only support ADL_CAW_xxx */
		if (strncmp(adl_capa, ADL_CAPA_CAW, LOCK_CAPA_LEN + 1)) {
			status = -EINVAL;
			goto bail;
		}
	} else if (!strncmp(lock_type, LOCK_TYPE_DLM, LOCK_TYPE_LEN + 1)) {
		/* DLM lock */
		*lock_stack = OCFS2_LOCK_DLM;
	}
	else {
		status = -EINVAL;
		goto bail;
	}

	uuid = strsep(&tmp_name, "_");
	if (!uuid) {
		status = -EINVAL;
		goto bail;
	} else if (strlen(uuid) > OCFS2_VOL_UUID_LEN * 2) {
		status = -EINVAL;
		mlog(ML_ERROR, "domain name %s len %zd too long.\n",
				uuid, strlen(uuid));
	} else {
		strncpy(domain_name, uuid, strlen(uuid) + 1);
	}

bail:
	return status;
}

/*
 * File creation. Allocate an inode, and we're done..
 */
/* SMP-safe */
static int lockfs_mkdir(struct inode *dir,
		       struct dentry *dentry,
		       umode_t mode)
{
	int status;
	struct inode *inode = NULL;
	unsigned int len = dentry->d_name.len;
	char d_name[GROUP_NAME_MAX];
	struct lockfs_inode_private *ip;
	void *conn;
	char domain_name[OCFS2_VOL_UUID_LEN * 2 + 1] = {0};
	enum ocfs2_lock_class lock_stack = OCFS2_LOCK_INVALID;

	/* format of directory must use like "ADL_${UUID}" or "DLM_${UUID}" */
	mlog(0, "mkdir %pd\n", dentry);

	/* verify that we have a proper domain */
	if (len >= GROUP_NAME_MAX) {
		status = -EINVAL;
		mlog(ML_ERROR, "%pd: invalid domain name for directory.\n", dentry);
		goto bail;
	}

	/* copy from dentry name in order to parse parameters */
	strncpy(d_name, dentry->d_name.name, len + 1);

	status = lockfs_handle_dname(d_name, &lock_stack,
			domain_name);
	if (status < 0) {
		mlog(ML_ERROR, "invalid lockfs dir name, %s.\n", d_name);
		goto bail;
	}

	if (lock_stack == OCFS2_LOCK_INVALID) {
		status = -EINVAL;
		mlog(ML_ERROR,
			"invalid lock stack, currently only support 'ADL_CAW_${UUID}' and 'DLM_${UUID}'.\n");
		goto bail;
	}

	mlog(0, "domain name: %s, lock stack: %s\n",
			domain_name, (lock_stack == OCFS2_LOCK_DLM) ? "DLM" : "ADL");

	inode = lockfs_get_inode(dir, dentry, mode | S_IFDIR);
	if (!inode) {
		status = -ENOMEM;
		mlog_errno(status);
		goto bail;
	}

	ip = LOCKFS_I(inode);

	if (lock_stack == OCFS2_LOCK_DLM)
		conn = user_dlm_register(domain_name, strlen(domain_name));
	else
		conn = user_adl_register(domain_name, strlen(domain_name));

	if (IS_ERR(conn)) {
		status = PTR_ERR(conn);
		mlog(ML_ERROR, "Error %d could not register domain \"%s\"\n",
			 status, domain_name);
		goto bail;
	}

	ip->ip_conn = conn;
	ip->ip_lock_stack = lock_stack;

	inc_nlink(dir);
	d_instantiate(dentry, inode);
	dget(dentry);	/* Extra count - pin the dentry in core */

	status = 0;
bail:
	if (status < 0)
		iput(inode);
	return status;
}

static int lockfs_create(struct inode *dir,
			struct dentry *dentry,
			umode_t mode,
			bool excl)
{
	int status = 0;
	struct inode *inode;
	struct qstr *name = &dentry->d_name;

	mlog(0, "create %.*s\n", name->len, name->name);

	/* verify name is valid and doesn't contain any dlm reserved
	 * characters */
	if (name->len >= USER_LOCK_ID_MAX_LEN ||
		   name->name[0] == '$') {
		status = -EINVAL;
		mlog(ML_ERROR, "o2dlm: invalid lock name, %.*s\n", name->len,
			    name->name);
		goto bail;
	}

	inode = lockfs_get_inode(dir, dentry, mode | S_IFREG);
	if (!inode) {
		status = -ENOMEM;
		mlog_errno(status);
		goto bail;
	}

	d_instantiate(dentry, inode);
	dget(dentry);	/* Extra count - pin the dentry in core */
bail:
	return status;
}

static int lockfs_unlink(struct inode *dir,
			struct dentry *dentry)
{
	int status;
	struct inode *inode = d_inode(dentry);
	struct lockfs_inode_private *ip = LOCKFS_I(inode);

	mlog(0, "unlink inode %lu\n", inode->i_ino);

	/* if there are no current holders, or none that are waiting
	 * to acquire a lock, this basically destroys our lockres. */
	if (ip->ip_lock_stack == OCFS2_LOCK_DLM)
		status = user_dlm_destroy_lock(&ip->ip_lockres);
	else
		status = user_adl_destroy_lock(&ip->ip_lockres);

	if (status < 0) {
		mlog(ML_ERROR, "unlink %pd, error %d from destroy\n",
		     dentry, status);
		goto bail;
	}

	status = simple_unlink(dir, dentry);
bail:
	return status;
}

static int lockfs_fill_super(struct super_block *sb,
			    void *data,
			    int silent)
{
	struct inode *inode;
	struct dentry *root;

	sb->s_maxbytes = MAX_LFS_FILESIZE;
	sb->s_blocksize = PAGE_SIZE;
	sb->s_blocksize_bits = PAGE_SHIFT;
	sb->s_magic = LOCKFS_MAGIC;
	sb->s_op = &lockfs_ops;
	inode = lockfs_get_root_inode(sb);
	if (!inode)
		return -ENOMEM;

	root = d_make_root(inode);
	if (!root) {
		iput(inode);
		return -ENOMEM;
	}
	sb->s_root = root;
	return 0;
}

static const struct file_operations lockfs_file_operations = {
	.open		= lockfs_file_open,
	.release	= lockfs_file_release,
	.poll		= lockfs_file_poll,
	.read		= lockfs_file_read,
	.write		= lockfs_file_write,
	.llseek		= default_llseek,
};

static const struct inode_operations lockfs_dir_inode_operations = {
	.create		= lockfs_create,
	.lookup		= simple_lookup,
	.unlink		= lockfs_unlink,
};

/* this way we can restrict mkdir to only the toplevel of the fs. */
static const struct inode_operations lockfs_root_inode_operations = {
	.lookup		= simple_lookup,
	.mkdir		= lockfs_mkdir,
	.rmdir		= simple_rmdir,
};

static const struct super_operations lockfs_ops = {
	.statfs		= simple_statfs,
	.alloc_inode	= lockfs_alloc_inode,
	.destroy_inode	= lockfs_destroy_inode,
	.evict_inode	= lockfs_evict_inode,
	.drop_inode	= generic_delete_inode,
};

static const struct inode_operations lockfs_file_inode_operations = {
	.getattr	= simple_getattr,
	.setattr	= lockfs_file_setattr,
};

static struct dentry *lockfs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	return mount_nodev(fs_type, flags, data, lockfs_fill_super);
}

static struct file_system_type lockfs_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ocfs2_lockfs",
	.mount		= lockfs_mount,
	.kill_sb	= kill_litter_super,
};

static int __init init_lockfs_fs(void)
{
	int status;
	int cleanup_inode = 0, cleanup_worker = 0;

	lockfs_inode_cache = kmem_cache_create("lockfs_inode_cache",
				sizeof(struct lockfs_inode_private),
				0, (SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
					 SLAB_MEM_SPREAD|SLAB_ACCOUNT),
				lockfs_init_once);
	if (!lockfs_inode_cache) {
		status = -ENOMEM;
		goto bail;
	}
	cleanup_inode = 1;

	user_dlm_worker = alloc_workqueue("user_dlm", WQ_MEM_RECLAIM, 0);
	if (!user_dlm_worker) {
		status = -ENOMEM;
		goto bail;
	}
	cleanup_worker = 1;

	user_dlm_set_locking_protocol();
	status = register_filesystem(&lockfs_fs_type);
bail:
	if (status) {
		if (cleanup_inode)
			kmem_cache_destroy(lockfs_inode_cache);
		if (cleanup_worker)
			destroy_workqueue(user_dlm_worker);
	} else
		mlog(ML_NOTICE, "OCFS2 User LOCK kernel interface loaded\n");
	return status;
}

static void __exit exit_lockfs_fs(void)
{
	unregister_filesystem(&lockfs_fs_type);

	destroy_workqueue(user_dlm_worker);

	/*
	 * Make sure all delayed rcu free inodes are flushed before we
	 * destroy cache.
	 */
	rcu_barrier();
	kmem_cache_destroy(lockfs_inode_cache);
}

MODULE_AUTHOR("Oracle");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("OCFS2 lock file system");

module_init(init_lockfs_fs)
module_exit(exit_lockfs_fs)
